import subprocess
import sys
import threading
from queue import Queue, Empty

ON_POSIX = 'posix' in sys.builtin_module_names


class Scrape:
    def __init__(self, sourceId):
        self.sourceId = sourceId
        self.subprocess = None
        self.type = None
        self.queue = Queue()

    def start(self):
        pass

    def get_scrape_identifier(self):
        return self.sourceId + '|' + self.type


class HistoryScrape(Scrape):
    def __init__(self, sourceId):
        super().__init__(sourceId)
        self.type = 'history'

    def start(self):
        self.subprocess = subprocess.Popen(['nohup', 'python', 'run_history_scrape.py', self.sourceId],
                                           stdout=subprocess.PIPE,
                                           stderr=subprocess.STDOUT,
                                           bufsize=1)
        self.t = threading.Thread(target=enqueue_output, args=(self.subprocess.stdout, self.queue))
        self.t.daemon = True
        self.t.start()


class LiveScrape(Scrape):
    def __init__(self, sourceId):
        """Example function with types documented in the docstring.

        `PEP 484`_ type annotations are supported. If attribute, parameter, and
        return types are annotated according to `PEP 484`_, they do not need to be
        included in the docstring:

        Args:
            param1 (int): The first parameter.
            param2 (str): The second parameter.

        Returns:
            bool: The return value. True for success, False otherwise.

        .. _PEP 484:
            https://www.python.org/dev/peps/pep-0484/

        """
        super().__init__(sourceId)
        self.type = 'live'

    def start(self):
        self.subprocess = subprocess.Popen(['nohup', 'python', 'run_live_scrape.py', self.sourceId],
                                           stdout=subprocess.PIPE,
                                           stderr=subprocess.STDOUT,
                                           bufsize=1)
        self.t = threading.Thread(target=enqueue_output, args=(self.subprocess.stdout, self.queue))
        self.t.daemon = True
        self.t.start()


def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line.rstrip())
    out.close()


class ScrapeManager:
    def __init__(self, logger):
        """Manages and starts scrapes in own subprocess."""

        self.logger = logger
        self.scrapes = dict()
        self.run_counter = dict()

    def poll_processes(self):
        """Polls subprocess. Logs output if they have finished"""
        ended_scrapes = []

        for key, scrape in self.scrapes.items():

            try:
                out = scrape.queue.get_nowait()
            except Empty:
                print('no output yet')
            else:
                # log output in main log file.
                self.logger.info('%s: %s', scrape.get_scrape_identifier(), out.decode('utf-8'))

            # delete finished scrape from list
            if scrape.queue.empty() and scrape.subprocess.poll() is not None:
                self.logger.info('%s has ended', scrape.get_scrape_identifier())
                ended_scrapes.append(key)

        for scrape in ended_scrapes:
            self.scrapes.pop(scrape)

    def add_scrape(self, scrape):
        """Polls subprocess. Logs output if they have finished"""

        key = scrape.get_scrape_identifier()

        if key in self.scrapes:
            self.logger.info('%s is still running and will not be started again', key)

            if key in self.run_counter:
                self.run_counter[key] += 1
                if self.run_counter[key] > 3:
                    self.logger.info('%s will be restarted', key)
                    self.scrapes[key].subprocess.kill()
                    self.scrapes.pop(key)
                    self.add_scrape(scrape)
            else:
                self.run_counter[key] = 1

        else:
            scrape.start()
            self.logger.info('starting scrape. source: %s', key)
            self.scrapes[key] = scrape
            self.run_counter[key] = 0
